js基础知识整理(编译、作用域,词法作用域,闭包,执行上下文,GC,this,继承等,万字总结)(三)
其他
自己实现一个new,call,bind
new
new调用上面已经实现过,现在来复习一下,不写代码,过一下流程。new调用是在this绑定一节中出现的,this绑定的方式主要是默认绑定(指向window),显式绑定(call,apply,bind,以及各种js内置api),隐式绑定主要是指向当前调用对象上下文,以及new调用(创建新对象),new调用执行了以下操作
- 1.创建一个新对象
- 2.将新对象的
__proto__指向函数的prototype - 3.执行函数,重新显式绑定
this,将this更改为新对象 - 4.如果函数本身有
return,并且是一个对象,就返回自身对象,否则返回新创建的对象。
call
思考以下代码,如何才能让foo指向bar?
1 | |
现在来实现一下
1 | |
bind
bind函数也是用来修改this的,不过函数不会立即执行。
- 1.获取传入的对象(
arguments)和调用的函数(this) - 2.返回一个函数
- 3.执行调用的函数,将
this指向传入的对象,并且考虑有参数情况1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 其实和上面call实现有点类似,不过函数不会立即执行
Function.prototype.bind2 = function () {
// 同样先获取传入的对象以及参数
// ctxObj:传入的bar对象,args:后面的参数,如果有
const [ctxObj,...args] = arguments
// 1.保存this
let self = this // this指向bind的调用者,就是foo函数本身
// 2.返回一个函数,因为不会立即执行
return function () {
// 3.再次获取arguments,因为返回的函数调用,也可以传递参数
const [bindargs] = arguments
// 执行函数并且指向传入的对象,后面的参数是需要将bind后面的参数和执行函数后面的参数拼接起来返回
return self.apply(ctxObj,[...args.concat(bindargs)])
}
}
一般最常见使用它来判断对象属于Object.prototype.toString原理js的哪种内置类型思考第一个问题,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(function(){}) // "[object Function]"
Object.prototype.toString.call(new RegExp) // "[object RegExp]"
Object.prototype.toString.call(new Date) // "[object Date]"
Object.prototype.toString.call(new Date) // "[object Date]"
Object.prototype.toString.call(new Error) // "[object Error]"
...Object.prototype.toString()和Object.toString()区别在哪里?实际上,这还是原型链的问题,之前在函数和对象的关系介绍中,我们知道,js内置对象本质就是函数,Object也不例外,所以,当我们调用Object.toString()方法时候,实际上,是在调用Function中的toString()方法,因为Object继承于Function,调用toString()会被Function中的同名方法重写覆盖,Number,Array等等都有自己的toString()方法
Object.prototype.toString原理是什么?
我记得红宝书第三版讲的是获取的是[[Class]]属性的值,这是ES3的东西。es6之后发生了变化。
说到这个,就不得不提到一个es6新增的类型,Symbol()。
Symbol可以接收一个字符串作为参数,也可以不传,只要调用就会创建一个保证不会覆盖的属性,并且不可以使用new关键字来实例化
1
2
3
4
5
6const name = Symbol()
const age = Symbol()
const gender = Symbol('man')
//重用Symbol
const job = Symbol.for('CS') // 创建一个Symbol
let other = Symbol.for('CS') //Object.prototype.tostring原理:
ECMA原文地址
可以看到,实际上,除了null和undefined做了特殊处理,剩下的获取的是builtinTag的值,什么是builtinTag?就是内置标签。
为什么说到Symbol,就是因为通通过Symbol.toStringTag设置builtinTag的值,这也就是Object.prototype.tostring的原理
1 | |
instanceof原理
instanceof,可以用来判断一个对象是否属于某个构造函数,也可以用来判断一个实例是否属于它的父类型。
1 | |
知道了上面prototype、__proto__之间的关系以及new操作符做的事情,就很容易知道实现原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function instanceof (L,R) {
// 先获取右侧的函数的prototype
var right = R.prototype
// 获取左侧对象的__proto__
var left = L.__proto__
while (true) {
// 判断是否全等,因为new调用,会创建新对象,将__proto__指向函数的prototype
if (left === right) {
return true
}
// 已经查找到Object.prototype了,没找到返回false
if (left === null) {
return false
}
// 下次循环继续查找下一个__proto__
left = left.__proto__
}
}
instanceof,typeof,Object.prototype.toString区别
in 和 hasOwnProperty区别
思考以下代码,如何区分是当前的值返回了undefined还是这个值不存在?
1
2
3
4
5
6
7var obj = {
a:undefined
}
obj.a // undefined
obj.b // undefined
可以通过in和hasOwnProperty来判断
1
2
3
4a in obj // true
b in obj // false
obj.hasOwnProperty("a"); // true
obj.hasOwnProperty("b"); // false
这两者的区别是in会查找原型链,而且检查的是属性名。hasOwnProperty只会查找对象自身
对象,包装对象,js内置对象
对象的声明方式
字面量形式
1
2
3
4var obj = {
key:value
}构造函数/(调用)形式
1
2var obj = new Object()
obj.key = valueObject.create()
语法:
Object.create(proto,[propertiesObject])创建一个新对象,新创建对象的__proto__就是传入的现有对象
proto:新创建对象的原型对象
propertiesObject:可选。要添加到新对象的可枚举(新添加的属性是其自身的属性,而不是其原型链上的属性)的属性,与Object.defineProperties第二个参数属性相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 var obj = Object.create({b:1})
obj.b // 1, obj的__proto__是{b:1}
var obj = Object.create(null)
obj // {},没有__proto__
var obj = Object.create({},{
a:{
value: 1,
writable: true
},
b:{
value: 2,
writable: true
}
})
console.log(obj.a) // 1
console.log(obj.b) // 2
字面量形式和构造调用方式没有什么太大区别,字面量形式可以一次声明多个,构造调用只能一个一个去赋值。另外最后的object.create()这种方式可以接收参数,创建出来的对象的__proto__就是传入的对象,如果是null,没有__proto__
js内置对象
js中的内置对象是对象的子类型,本质上是函数,通过new操作符调用
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
- Error
包装对象
什么是包装对象?比如以下代码
1 | |
我们的str只是一个字面量,为什么可以向对象一样去调用length,substring等这些方法?实际上,如果我们想去像对象一样操作,就必须转换为对象,只是js引擎会自动去做这些事情,并不需要我们手动操作。之前也说过new调用会创建一个新对象。以下这几种都是包装对象
String
Number
Boolean
1
2
3
4
5
6
7
8
9
10
11
12
13
14var str = 'i am a string'
// 相当于
var str = new String('i am a string')
console.log(str) // String {0:i,1:'',2:'a',3:'m',length:13}
var num = 100
// 相当于
var num = new Number(100)
console.log(num) // Number {100}
var boo = true
// 相当于
var boo = new Boolean(true)
console.log(boo) // Boolean {true}对象查找和对象中的[[Get]],[[Put]]
如下代码,当我们访问
obj.a时候会发生什么?1
2
3
4var obj = {
a:1
}
obj.a // 1看上去似乎直接将访问的变量返回,但实际上实现了一个
[[Get]]操作,类似[[Get]]()函数调用首先会在对象中查找是否有相同名称属性,找到就返回这个值,
没有找到,就会查找
[[ProtoType]](__proto__),如果还查找不到,就会返回
undefined。1
2
3
4
5
6
7
8// 类似如下代码
var obj = {
get a () {
return 1
}
}
obj.a // 1另外一个就是
[[Put]],也就是修改值发生的事情,大致如下属性是否设置了属性描述符?,存在
setter就执行setter属性描述符的
writable是否是false?如果都不是,就设置这个值
如果是被proxy代理的情况下,这里的
[[Get]]和[[Put]]并不适用
参考资料:
https://juejin.cn/post/6844903953981767688 JavaScript:V8编译过程
https://juejin.cn/post/6961349015346610184 Event Loop 和 JS
引擎、渲染引擎的关系
https://www.cnblogs.com/wangfupeng1988/p/3977924.html 深入理解javascript原型和闭包(完结)
https://blog.csdn.net/szengtal/article/details/78726143 词法环境:ECMA实现(一)— 简介
https://juejin.cn/post/6926831181681917959#heading-6 详解ES6执行时词法环境/作用域/执行上下文/执行栈和闭包
https://262.ecma-international.org ECMA原文地址
本文标题:js基础知识整理(编译、作用域,词法作用域,闭包,执行上下文,GC,this,继承等,万字总结)(三)
文章作者:Niuhk
发布时间:2021-07-22
最后更新:2022-01-23
原始链接:https://www.niuhk.cn/2021/07/22/js基础知识整理(编译、作用域,词法作用域,闭包,执行上下文,GC,this,继承等,万字总结)(三) /
版权声明:转载请注明出处!
分享